/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package controller;

import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.UnsupportedCommOperationException;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.TooManyListenersException;
import java.util.logging.Level;
import java.util.logging.Logger;

import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.geometry.Insets;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBar.ButtonData;
import javafx.scene.control.ButtonType;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ColorPicker;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Dialog;
import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.control.RadioMenuItem;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.control.ToggleGroup;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import model.CpdetectorUtils;
import model.SerialOpenAndClose;
import model.SerialPortUtil;

/**
 * FXML Controller class
 *
 * @author wxhntmy
 */
public class HomePageViewController implements Initializable {

    /**
     * 菜单栏
     */
    @FXML
    private MenuBar menubar;
    /**
     * 清除窗口按键
     */
    @FXML
    private Button btClearWindow;
    /**
     * 打开文件按键
     */
    @FXML
    private Button btOpenFile;
    /**
     * 显示区
     */
    @FXML
    private TextArea displayTextArea;
    /**
     * 显示打开的文件地址
     */
    @FXML
    private TextField fileTextField;
    /**
     * 发送文件按键
     */
    @FXML
    private Button btSendFile;
    /**
     * 小的发送按键
     */
    @FXML
    private Button btSend;
    /**
     * 清空发送区按键
     */
    @FXML
    private Button btClearSendBuff;
    /**
     * 串口列表
     */
    @FXML
    private ComboBox<String> comList;
    /**
     * 打开串口按键
     */
    @FXML
    private Button btOpenSerial;
    /**
     * 大的发送按键
     */
    @FXML
    private Button btOpenSend;
    /**
     * 发送区
     */
    @FXML
    private TextArea sendTextArea;
    /**
     * 波特率列表
     */
    @FXML
    private ComboBox<Integer> comBaudRate;
    /**
     * 数据位列表
     */
    @FXML
    private ComboBox<Integer> comDataBits;
    /**
     * 停止位列表
     */
    @FXML
    private ComboBox<String> comStopBits;
    /**
     * 校验方式列表
     */
    @FXML
    private ComboBox<String> comParity;
    /**
     * 发送数据长度显示
     */
    @FXML
    private Label sendDataLength;
    /**
     * 接收数据长度显示
     */
    @FXML
    private Label receiveDataLength;
    /**
     * 串口配置显示
     */
    @FXML
    private Label serialUtilDisplay;
    /**
     * 加回车换行
     */
    @FXML
    private CheckBox lineFeedCheckBox;
    /**
     * 显示缓冲上限200K
     */
    @FXML
    private RadioMenuItem displayBufferUpperLimit200K;
    /**
     * 显示缓冲上限500K
     */
    @FXML
    private RadioMenuItem displayBufferUpperLimit500K;
    /**
     * 显示缓冲上限1M
     */
    @FXML
    private RadioMenuItem displayBufferUpperLimit1M;
    /**
     * 显示缓冲上限2M
     */
    @FXML
    private RadioMenuItem displayBufferUpperLimit2M;
    /**
     * 显示缓冲上限5M
     */
    @FXML
    private RadioMenuItem displayBufferUpperLimit5M;
    /**
     * 回车后发送
     */
    @FXML
    private RadioMenuItem enterSend;
    /**
     * 发送按键键值
     */
    @FXML
    private RadioMenuItem sendButtonValue;
    /**
     * 本地回显按键键值
     */
    @FXML
    private RadioMenuItem displayButtonValue;
    /**
     * 发送回车0x0a后自动附加换行符0x0d
     */
    @FXML
    private RadioMenuItem sendEnterAndNewlineCharacter;
    /**
     * 刷新串口列表
     */
    @FXML
    private MenuItem refreshSerialPortList;
    /**
     * 设置字体/颜色/大小
     */
    @FXML
    private MenuItem fontColorSize;
    /**
     * 背景颜色
     */
    @FXML
    private MenuItem backgroundColor;
    /**
     * 显示缓冲上限帮助
     */
    @FXML
    private MenuItem displayBufferUpperLimitHelp;
    /**
     * 发送帮助
     */
    @FXML
    private MenuItem sendHelp;
    /**
     * 多字符串
     */
    @FXML
    private Menu multipleStrings;
    /**
     * 小工具
     */
    @FXML
    private Menu gadget;
    /**
     * 帮助
     */
    @FXML
    private MenuItem help;
    /**
     * 联系作者
     */
    @FXML
    private MenuItem contactAuthor;

    /**
     * 显示缓冲上限单选框
     */
    ToggleGroup displayBufferUpperLimitGroup = new ToggleGroup();

    /**
     * 串口列表
     */
    private List<String> portList = null;
    /**
     * 串口参数
     */
    private SerialOpenAndClose parameter = new SerialOpenAndClose();
    /**
     * 串口对象
     */
    private SerialPort serialPort = null;
    /**
     * 已发送的数据长度
     */
    private Integer sendLength = 0;
    /**
     * 已接收的数据长度
     */
    private Integer receiveLength = 0;
    /**
     * 默认串口名字
     */
    private String serialPortName = "COM1";
    /**
     * 默认串口状态
     */
    private String serialPortStatus = "已关闭";

    /**
     * f为选择到的文件
     */
    private File f = null;

    /**
     * 上一次打开文件的目录路径
     */
    private String LastFilePath = "";

    /**
     * 默认显示缓冲上限100K
     */
    private long handledisplayBufferUpperLimit = 102400;

    /**
     * 关闭串口字符常量
     */
    private final String CLOSE_SERIAL_PORT = "关闭串口";

    /**
     * 打开串口字符常量
     */
    private final String OPEN_SERIAL_PORT = "打开串口";

    /**
     * 初始化控制类
     *
     * @param url 无
     * @param rb  无
     */
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // TODO
        //加入combobox列表
        comBaudRate.getItems().addAll(110, 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 38400, 56000, 57600, 74880, 115200, 128000, 230400, 256000, 460800, 500000, 512000, 600000, 750000, 921600, 1000000, 1500000, 2000000);
        comDataBits.getItems().addAll(5, 6, 7, 8);
        comStopBits.getItems().addAll("1", "1.5", "2");
        comParity.getItems().addAll("None", "Odd", "Even", "Mark", "Space");
        if (getSystemPortList()) {
            for (String s : portList) {
                comList.getItems().add(s);
            }
        }

        //将单选框加入到同一个组里，同一个组里的单选键只能被选中一个
        displayBufferUpperLimitGroup.getToggles().add(displayBufferUpperLimit200K);
        displayBufferUpperLimitGroup.getToggles().add(displayBufferUpperLimit500K);
        displayBufferUpperLimitGroup.getToggles().add(displayBufferUpperLimit1M);
        displayBufferUpperLimitGroup.getToggles().add(displayBufferUpperLimit2M);
        displayBufferUpperLimitGroup.getToggles().add(displayBufferUpperLimit5M);

        //设置显示区自动换行
        displayTextArea.setWrapText(true);

        //设置串口初始值，防止空指针
        comBaudRate.setValue(9600);
        comDataBits.setValue(8);
        comStopBits.setValue("1");
        comParity.setValue("None");
        comList.setValue("COM1");
        //显示初始接收数据长度
        receiveDataLength.setText("Receive: " + receiveLength);
        //显示初始发送数据长度
        sendDataLength.setText("Send: " + sendLength);
        //显示串口初始值
        serialUtilDisplay.setText(serialPortName + "   " + serialPortStatus + "   " + comBaudRate.getValue()
                + "bps, " + comDataBits.getValue() + ", " + comStopBits.getValue() + ", " + comParity.getValue());

        //默认按Ctrl+Enter发送
        enterSend.setSelected(true);
    }

    /**
     * 大的发送按键单击事件
     *
     * @param event 按键单击事件
     */
    @FXML
    private void handlebtOpenSendAction(ActionEvent event) {
        //打开了串口才能发送
        if (CLOSE_SERIAL_PORT.equals(btOpenSerial.getText())) {
            String s = sendTextArea.getText();
            //发送区不为空才能发送
            if (!s.isEmpty()) {
                //选中了加换行符发送
                if (lineFeedCheckBox.isSelected()) {
                    s = s + "\r\n";
                }
                //选中了发送回车0x0a后自动附加换行符0x0d
                if (sendEnterAndNewlineCharacter.isSelected()) {
                    if (s.substring(s.length() - 1).equals(0x0D)) {
                        s = s + 0x0A;
                    }
                }
                sendData(s);
            } else {
                alert(AlertType.WARNING,"警告", "发送内容不能为空！");
            }
        } else {
            alert(AlertType.WARNING,"警告", "串口还没有打开！");
        }
    }

    /**
     * 发送数据并更新显示区
     * 此方法默认发送字符编码格式为GBK
     * @param data 需要发送的数据
     */
    public void sendData(String data){
        //把字符串转为二进制数组
        byte[] bytes = new byte[0];
        try {
            bytes = data.getBytes("GBK");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        //记录发送的数据长度
        sendLength = sendLength + bytes.length;
        //发送数据
        SerialPortUtil.sendData(serialPort, bytes, "GBK");
        if (displayTextArea.getText().length() < handledisplayBufferUpperLimit) {
            displayTextArea.appendText("发→：" + data + "\r\n");
            //显示更新的发送的数据长度
            sendDataLength.setText("Send: " + sendLength);
        } else {
            displayTextArea.appendText("已达显示区缓冲上限！\n");
        }
    }

    /**
     * 设置串口接收监听事件
     */
    public void getSerialPortActionEvent() {
        try {
            SerialPortUtil.setListenerToSerialPort(serialPort, events -> {
                //数据通知
                if (events.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
                    //读串口数据
                    byte[] bytess = SerialPortUtil.readData(serialPort);
                    //更新接收的数据长度
                    receiveLength = receiveLength + bytess.length;
                    if (displayTextArea.getText().length() < handledisplayBufferUpperLimit) {
                        //显示接收的数据
                        try {
                            displayTextArea.appendText("收←：" + new String(bytess, "GBK") + "\r\n");
                        } catch (UnsupportedEncodingException e) {
                            e.printStackTrace();
                        }
                        /*
                          Java FX程序不允许用户在FX application thread线程外更新界面的组件信息
                          即所有的更新界面组件的信息的代码，都应该在在FX application thread线程中执行。
                          用Platform类的runLater方法可以解决该问题
                          该方法可以将参数runnable将要执行的代码，交给FX application thread线程执行，这样问题就解决了
                         */
                        Platform.runLater(() -> {
                            //任何更新界面组件信息的代码
                            //显示更新的接收的数据长度
                            receiveDataLength.setText("Receive: " + receiveLength);
                        });
                    } else {
                        displayTextArea.appendText("已达显示区缓冲上限！\n");
                    }
                }
            });
        } catch (TooManyListenersException ex) {
            Logger.getLogger(HomePageViewController.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * 获取串口列表
     *
     * @return 如果串口列表不为空，返回true
     */
    public boolean getSystemPortList() {
        //获取系统可用的串口列表
        portList = SerialPortUtil.getSerialPortList();
        //如果串口列表不为空，返回true
        return !portList.isEmpty();
    }

    /**
     * 清除窗口按键单击事件
     *
     * @param event 按键单击事件
     */
    @FXML
    private void handlebtClearWindowAction(ActionEvent event) {
        //清空显示区
        displayTextArea.setText("");
        //清空打开的文件的绝对地址
        fileTextField.setText("");
        //清空接收的数据长度
        receiveLength = 0;
        //清空发送的数据长度
        sendLength = 0;
        //显示新的接收的数据长度
        receiveDataLength.setText("Receive: " + receiveLength);
        //显示新的发送的数据长度
        sendDataLength.setText("Send: " + sendLength);
    }

    /**
     * 打开文件按键单击事件
     *
     * @param event 按键单击事件
     */
    @FXML
    private void handlebtOpenFileAction(ActionEvent event) {
        //创建FileChooser需要一个新的Stage
        Stage stage = new Stage();
        //新建FileChooser选择文件
        FileChooser fileChooser = new FileChooser();
        //打开上一次打开的文件夹
        if (!"".equals(LastFilePath)){
            fileChooser.setInitialDirectory(new File(LastFilePath));
        }
        fileChooser.setTitle("打开文件");
        fileChooser.getExtensionFilters().addAll(
                new FileChooser.ExtensionFilter("All Files", "*.*"));
        f = fileChooser.showOpenDialog(stage);

        //选择到的文件不为空
        if (null != f){

            LastFilePath = f.getParent();

            //显示选择到的文件的绝对地址
            fileTextField.setText(f.getAbsolutePath());

            /*
              此处的读取文件编码格式只是为了解决汉字乱码的问题
             */
            String charsetName = CpdetectorUtils.getFileCharset(f.getAbsolutePath());
            System.out.println("Open File CharsetName: " + charsetName);

            //文件内容的读取
            //InputStreamReader类是从字节流到字符流的桥接器：它使用指定的字符集读取字节并将它们解码为字符。
            InputStreamReader reader = null;
            try {
                reader = new InputStreamReader(new FileInputStream(f), charsetName);
            } catch (FileNotFoundException | UnsupportedEncodingException ex) {
                Logger.getLogger(HomePageViewController.class.getName()).log(Level.SEVERE, null, ex);
            }

            if (reader == null){
                alert(AlertType.WARNING,"警告", "打开的文件为空文件！");
                return;
            }
            //BufferedReader类从字符输入流中读取文本并缓冲字符，以便有效地读取字符，数组和行
            BufferedReader bfreader = new BufferedReader(reader);
            //缓冲BufferedReader读取的每一行字符
            String line;
            try {
                displayTextArea.setText("");
                displayTextArea.appendText("文件大小: " + f.length() + " 字节，下面是预览的前 " + f.length() + " 字节内容：\n");
                //包含该行内容的字符串，不包含任何行终止符，如果已到达流末尾，则返回 null
                while ((line = bfreader.readLine()) != null) {
                    if (displayTextArea.getText().length() < handledisplayBufferUpperLimit) {
                        displayTextArea.appendText(line + "\n");
                    } else {
                        displayTextArea.appendText("已达显示区缓冲上限！\n");
                    }
                }
            } catch (IOException ex) {
                Logger.getLogger(HomePageViewController.class.getName()).log(Level.SEVERE, null, ex);
            }
            try {
                //关闭InputStreamReader
                reader.close();
                //关闭BufferedReader
                bfreader.close();
            } catch (IOException ex) {
                Logger.getLogger(HomePageViewController.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        else{
            alert(AlertType.WARNING,"警告", "未选择文件！");
        }

    }


    /**
     * 发送文件按键单击事件
     *
     * @param event 按键单击事件
     */
    @FXML
    private void handlebtSendFileAction(ActionEvent event) {
        //文件内容的读取
        if (f != null) {
            InputStreamReader reader = null;
            String charsetName = CpdetectorUtils.getFileCharset(f.getAbsolutePath());
            System.out.println("Open File CharsetName: " + charsetName);
            try {
                reader = new InputStreamReader(new FileInputStream(f), charsetName);
            } catch (FileNotFoundException | UnsupportedEncodingException ex) {
                Logger.getLogger(HomePageViewController.class.getName()).log(Level.SEVERE, null, ex);
            }
            if (reader == null){
                alert(AlertType.WARNING,"警告", "打开的文件为空文件！");
                return;
            }
            //读取
            BufferedReader bfreader = new BufferedReader(reader);
            //按行读取文件数据
            String line;
            try {
                displayTextArea.setText("");
                displayTextArea.appendText("文件大小: " + f.length() + "\n");
                displayTextArea.appendText("波特率 " + parameter.getBaudRate() + "bps\n");
                displayTextArea.appendText("请稍后...\n");
                //包含该行内容的字符串，不包含任何行终止符，如果已到达流末尾，则返回 null

                while ((line = bfreader.readLine()) != null) {
                    line = line + "\n";
                    //注意：此处获取字符串的二进制编码格式与文件编码格式一致
                    byte[] bytes = line.getBytes(charsetName);
                    //按行发送数据，以文件编码格式发送数据
                    SerialPortUtil.sendData(serialPort, bytes, charsetName);
                    //更新实际发送的数据长度
                    sendLength = sendLength + bytes.length;
                    //显示更新后的数据长度
                    sendDataLength.setText("Send: " + sendLength);
                }
                displayTextArea.appendText("发送完毕！\n");
            } catch (IOException ex) {
                Logger.getLogger(HomePageViewController.class.getName()).log(Level.SEVERE, null, ex);
            }
            try {
                reader.close();
            } catch (IOException ex) {
                Logger.getLogger(HomePageViewController.class.getName()).log(Level.SEVERE, null, ex);
            }
            try {
                bfreader.close();
            } catch (IOException ex) {
                Logger.getLogger(HomePageViewController.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    /**
     * 小的发送按键单击事件
     *
     * @param event 按键单击事件
     */
    @FXML
    private void handlebtSendAction(ActionEvent event) {
        handlebtOpenSendAction(event);
    }

    /**
     * 清除发送区按键单击事件
     *
     * @param event 按键单击事件
     */
    @FXML
    private void handlebtClearSendBuffAction(ActionEvent event) {
        sendTextArea.setText("");
    }

    /**
     * 根据ComStopBits的值设置停止位
     */
    public void setStopBits(){
        int stopBit = 0;
        /*
          停止位1字符常量
         */
        String STOP_BITS_1 = "1";
        if (STOP_BITS_1.equals(comStopBits.getValue())) {
            stopBit = SerialPort.STOPBITS_1;
        }
        /*
          停止位1.5字符常量
         */
        String STOP_BITS_1_5 = "1.5";
        if (STOP_BITS_1_5.equals(comStopBits.getValue())) {
            stopBit = SerialPort.STOPBITS_1_5;
        }
        /*
          停止位2字符常量
         */
        String STOP_BITS_2 = "2";
        if (STOP_BITS_2.equals(comStopBits.getValue())) {
            stopBit = SerialPort.STOPBITS_2;
        }
        //停止位
        parameter.setStopBits(stopBit);
    }

    /**
     * 设置校验方式
     */
    public void setParity(){
        String PARITY_NONE = "None";
        String PARITY_ODD = "Odd";
        String PARITY_EVEN = "Even";
        String PARITY_MARK = "Mark";
        String PARITY_SPACE = "Space";

        int parity = -1;
        if (PARITY_NONE.equals(comParity.getValue())) {
            parity = SerialPort.PARITY_NONE;
        }
        if (PARITY_ODD.equals(comParity.getValue())) {
            parity = SerialPort.PARITY_ODD;
        }
        if (PARITY_EVEN.equals(comParity.getValue())) {
            parity = SerialPort.PARITY_EVEN;
        }
        if (PARITY_MARK.equals(comParity.getValue())) {
            parity = SerialPort.PARITY_MARK;
        }
        if (PARITY_SPACE.equals(comParity.getValue())) {
            parity = SerialPort.PARITY_SPACE;
        }
        //校验方式
        parameter.setParity(parity);
    }

    /**
     * 打开串口按键单击事件
     *
     * @param event 按键单击事件
     * @return 串口状态。1：已打开。2：对应串口不存在。3：串口在使用中。4：不支持操作。
     */
    @FXML
    private Integer handlebtOpenSerialAction(ActionEvent event) {
        //串口名
        parameter.setSerialPortName(comList.getValue());
        //波特率
        parameter.setBaudRate(comBaudRate.getValue());
        //数据位
        parameter.setDataBits(comDataBits.getValue());

        setStopBits();
        setParity();


        int status = 0;
        if (OPEN_SERIAL_PORT.equals(btOpenSerial.getText())) {
            try {
                //打开串口
                serialPort = SerialPortUtil.openSerialPort(parameter);
                serialPortName = parameter.getSerialPortName();
                serialPortStatus = "已开启";
                serialUtilDisplay.setText(serialPortName + "   " + serialPortStatus + "   " + parameter.getBaudRate()
                        + "bps, " + parameter.getDataBits() + ", " + comStopBits.getValue() + ", " + comParity.getValue());
                //打开串口之后开启串口监听
                getSerialPortActionEvent();
                status = 1;
            } catch (NoSuchPortException ex) {
                //对应串口不存在
                status = 2;
                Logger.getLogger(HomePageViewController.class.getName()).log(Level.SEVERE, null, ex);
            } catch (PortInUseException ex) {
                //串口在使用中
                status = 3;
                Logger.getLogger(HomePageViewController.class.getName()).log(Level.SEVERE, null, ex);
            } catch (UnsupportedCommOperationException ex) {
                //不支持操作
                status = 4;
                Logger.getLogger(HomePageViewController.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        if (status == 1) {
            //设置字体颜色为红色
            btOpenSerial.setTextFill(Paint.valueOf("red"));
            //字体加粗
            btOpenSerial.setFont(Font.font("System", FontWeight.BOLD, 24));
            btOpenSerial.setText(CLOSE_SERIAL_PORT);
            //return返回
            return 1;
        }
        if (status == 2) {
            alert(AlertType.WARNING,"警告", "串口" + parameter.getSerialPortName() + "不存在！");
            return 2;
        }
        if (status == 3) {
            alert(AlertType.WARNING,"警告", "串口" + parameter.getSerialPortName() + "正在使用中！");
            return 3;
        }
        if (status == 4) {
            alert(AlertType.WARNING,"警告", "串口" + parameter.getSerialPortName() + "不支持操作！");
            return 4;
        }
        if (CLOSE_SERIAL_PORT.equals(btOpenSerial.getText())) {
            //设置字体颜色为黑色
            btOpenSerial.setTextFill(Paint.valueOf("black"));
            //字体粗细正常
            btOpenSerial.setFont(Font.font("System", FontWeight.NORMAL, 24));
            btOpenSerial.setText(OPEN_SERIAL_PORT);
            //关闭串口
            closeSerialPort();
            return 5;
        }
        return 6;
    }

    /**
     * 关闭串口
     */
    public void closeSerialPort() {
        //关闭已打开的串口
        SerialPortUtil.closeSerialPort(serialPort);
        serialPortStatus = "已关闭";
        //更新串口状态
        serialUtilDisplay.setText(serialPortName + "   " + serialPortStatus + "   " + parameter.getBaudRate()
                + "bps, " + parameter.getDataBits() + ", " + comStopBits.getValue() + ", " + comParity.getValue());
    }

    /**
     * 串口列表combobox点击事件
     *
     * @param event 点击事件
     */
    @FXML
    private void handlecomListAction(ActionEvent event) {
        serialUtilDisplay.setText(comList.getValue() + "   " + serialPortStatus + "   " + comBaudRate.getValue()
                + "bps, " + comDataBits.getValue() + ", " + comStopBits.getValue() + ", " + comParity.getValue());
        //设置串口名称
        parameter.setSerialPortName(comList.getValue());
    }

    /**
     * 波特率列表combobox点击事件
     *
     * @param event 点击事件
     */
    @FXML
    private void handlecomBaudRateAction(ActionEvent event) {
        serialUtilDisplay.setText(comList.getValue() + "   " + serialPortStatus + "   " + comBaudRate.getValue()
                + "bps, " + comDataBits.getValue() + ", " + comStopBits.getValue() + ", " + comParity.getValue());
        //设置波特率
        parameter.setBaudRate(comBaudRate.getValue());
    }

    /**
     * 数据位列表combobox点击事件
     *
     * @param event 点击事件
     */
    @FXML
    private void handlecomDataBitsAction(ActionEvent event) {
        serialUtilDisplay.setText(comList.getValue() + "   " + serialPortStatus + "   " + comBaudRate.getValue()
                + "bps, " + comDataBits.getValue() + ", " + comStopBits.getValue() + ", " + comParity.getValue());
        //设置数据位
        parameter.setDataBits(comDataBits.getValue());
    }

    /**
     * 停止位列表combobox点击事件
     *
     * @param event 点击事件
     */
    @FXML
    private void handlecomStopBitsAction(ActionEvent event) {
        serialUtilDisplay.setText(comList.getValue() + "   " + serialPortStatus + "   " + comBaudRate.getValue()
                + "bps, " + comDataBits.getValue() + ", " + comStopBits.getValue() + ", " + comParity.getValue());
        setStopBits();
    }

    /**
     * 校验方式列表combobox点击事件
     *
     * @param event 点击事件
     */
    @FXML
    private void handlecomParityAction(ActionEvent event) {
        serialUtilDisplay.setText(comList.getValue() + "   " + serialPortStatus + "   " + comBaudRate.getValue()
                + "bps, " + comDataBits.getValue() + ", " + comStopBits.getValue() + ", " + comParity.getValue());
        setParity();
    }

    /**
     * 菜单：更新串口列表点击事件
     *
     * @param event 点击事件
     */
    @FXML
    private void handlerefreshSerialPortListAction(ActionEvent event) {
        //刷新串口列表之前先清空原来的串口列表
        comList.getItems().clear();
        //获取系统可用的串口列表
        if (getSystemPortList()) {
            if (comList != null) {
                for (String s : portList) {
                    //更新串口列表
                    comList.getItems().add(s);
                }
            }
        }
        comList.setValue("COM1");
    }

    /**
     * 菜单：字体/颜色/大小点击事件
     *
     * @param event 点击事件
     */
    @FXML
    private void handlefontColorSizeAction(ActionEvent event) {
        //设置dialog的返回类型，dialog返回的是List类型.
        Dialog<List> dialog;
        dialog = new Dialog<>();
        //dialog标题
        dialog.setTitle("设置字体/颜色/大小");
        dialog.setHeaderText(null);

        //设置按键类型.
        ButtonType confirmButtonType = new ButtonType("确定", ButtonData.OK_DONE);
        dialog.getDialogPane().getButtonTypes().addAll(confirmButtonType, ButtonType.CANCEL);

        //设置网格布局窗格GridPane
        GridPane grid = new GridPane();
        //列之间水平间隙的宽度
        grid.setHgap(10);
        //行之间垂直间隙的高度。
        grid.setVgap(10);
        //构造具有四个不同偏移的新插入实例。
        grid.setPadding(new Insets(20, 150, 10, 10));

        //字体列表
        ComboBox<String> comFont = new ComboBox();
        //获取当前的字体
        comFont.setValue(displayTextArea.getFont().getFamily());
        //获取系统已安装的字体
        List<String> mylist = Font.getFamilies();
        for (String s : mylist) {
            comFont.getItems().add(s);
        }

        //颜色选择器
        final ColorPicker colorPicker = new ColorPicker();
        colorPicker.setValue(Color.CORAL);

        //字体大小列表
        ComboBox<Integer> comSize = new ComboBox();
        //获取当前的字体大小
        Double fontSize = displayTextArea.getFont().getSize();
        comSize.setValue(fontSize.intValue());
        comSize.getItems().addAll(15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30);

        grid.add(new Label("字体："), 0, 0);
        grid.add(comFont, 1, 0);
        grid.add(new Label("颜色："), 0, 1);
        grid.add(colorPicker, 1, 1);
        grid.add(new Label("字体大小："), 0, 2);
        grid.add(comSize, 1, 2);

        //把网格布局窗格添加到dialog里
        dialog.getDialogPane().setContent(grid);
        //单击确定按钮时，将结果转换为List返回。
        dialog.setResultConverter(dialogButton -> {
            if (dialogButton == confirmButtonType) {
                List list = new ArrayList();
                list.add(comFont.getValue());
                list.add(colorPicker.getValue());
                list.add(comSize.getValue());
                return list;
            }
            return null;
        });
        //接收dialog返回的List
        Optional<List> result = dialog.showAndWait();

        result.ifPresent(list -> {
            //设置字体和字体大小
            displayTextArea.setFont(Font.font(list.get(0).toString(), FontWeight.NORMAL, Integer.parseInt(list.get(2).toString())));
            //设置字体颜色
            displayTextArea.setStyle("-fx-text-fill: #" + list.get(1).toString().substring(2, list.get(1).toString().length()));
        });
    }

    /**
     * 菜单：背景颜色点击事件
     *
     * @param event 点击事件
     */
    @FXML
    private void handlebackgroundColorAction(ActionEvent event) {
        //设置dialog的返回类型，dialog返回的是List类型.
        Dialog<List> dialog;
        dialog = new Dialog<>();
        //dialog标题
        dialog.setTitle("设置背景颜色");
        dialog.setHeaderText(null);

        //设置按键类型.
        ButtonType confirmButtonType = new ButtonType("确定", ButtonData.OK_DONE);
        dialog.getDialogPane().getButtonTypes().addAll(confirmButtonType, ButtonType.CANCEL);

        //设置网格布局窗格GridPane
        GridPane grid = new GridPane();
        //列之间水平间隙的宽度
        grid.setHgap(10);
        //行之间垂直间隙的高度。
        grid.setVgap(10);
        //构造具有四个不同偏移的新插入实例。
        grid.setPadding(new Insets(20, 150, 10, 10));

        //颜色选择器
        final ColorPicker colorPicker = new ColorPicker();
        colorPicker.setValue(Color.CORAL);

        grid.add(new Label("背景颜色："), 0, 0);
        grid.add(colorPicker, 1, 0);

        //把网格布局窗格添加到dialog里
        dialog.getDialogPane().setContent(grid);

        //单击确定按钮时，将结果转换为List返回。
        dialog.setResultConverter(dialogButton -> {
            if (dialogButton == confirmButtonType) {
                List list = new ArrayList();
                list.add(colorPicker.getValue());
                return list;
            }
            return null;
        });

        //接收dialog返回的List
        Optional<List> result = dialog.showAndWait();

        result.ifPresent(list -> {
            displayTextArea.setStyle("-fx-background-color: #" + list.get(0).toString().substring(2, list.get(0).toString().length()));
        });

    }

    /**
     * 菜单：显示缓冲上限200K点击事件
     *
     * @param event 点击事件
     */
    @FXML
    private void handledisplayBufferUpperLimit200KAction(ActionEvent event) {
        handledisplayBufferUpperLimit = 1024 * 200;
    }

    /**
     * 菜单：显示缓冲上限500K点击事件
     *
     * @param event 点击事件
     */
    @FXML
    private void handledisplayBufferUpperLimit500KAction(ActionEvent event) {
        handledisplayBufferUpperLimit = 1024 * 500;
    }

    /**
     * 菜单：显示缓冲上限1M点击事件
     *
     * @param event 点击事件
     */
    @FXML
    private void handledisplayBufferUpperLimit1MAction(ActionEvent event) {
        handledisplayBufferUpperLimit = 1024 * 1024;
    }

    /**
     * 菜单：显示缓冲上限2M点击事件
     *
     * @param event 点击事件
     */
    @FXML
    private void handledisplayBufferUpperLimit2MAction(ActionEvent event) {
        handledisplayBufferUpperLimit = 1024 * 1024 * 2;
    }

    /**
     * 菜单：显示缓冲上限5M点击事件
     *
     * @param event 点击事件
     */
    @FXML
    private void handledisplayBufferUpperLimit5MAction(ActionEvent event) {
        handledisplayBufferUpperLimit = 1024 * 1024 * 5;
    }

    /**
     * 菜单：显示缓冲上限帮助点击事件
     *
     * @param event 点击事件
     */
    @FXML
    private void handledisplayBufferUpperLimitHelpAction(ActionEvent event) {
        alert(AlertType.INFORMATION,"发送帮助", "需要帮助请联系作者！");
    }

    /**
     * 菜单：回车发送点击事件
     *
     * @param event 点击事件
     */
    @FXML
    private void handleenterSendAction(ActionEvent event) {
        if (sendButtonValue.isSelected() && displayButtonValue.isSelected()) {
            sendButtonValue.setSelected(false);
        }
    }

    /**
     * 输入区键值检测
     *
     * @param event 按键输入事件
     */
    @FXML
    private void getButtonValueAction(KeyEvent event) {
        //检测到回车发送
        //菜单：回车发送被选中
        if (enterSend.isSelected()){
            //检测键值是否为回车键
            if (event.getCode() == KeyCode.ENTER) {
                //打开了串口才能发送
                if (CLOSE_SERIAL_PORT.equals(btOpenSerial.getText())) {
                    String s = sendTextArea.getText();
                    //发送区不为空才能发送
                    if (!s.isEmpty()) {
                        //选中了加换行符发送
                        if (lineFeedCheckBox.isSelected()) {
                            s = s + "\r\n";
                        }
                        sendData(s);
                    }
                }
            }
        }
        //按键立即发送该键值
        if (sendButtonValue.isSelected()) {
            KeyCode code = event.getCode();
            //打开了串口才能发送
            if (CLOSE_SERIAL_PORT.equals(btOpenSerial.getText())) {
                String s = code.toString();
                //选中了加换行符发送
                if (lineFeedCheckBox.isSelected()) {
                    s = s + "\r\n";
                }
                sendData(s);
            }
        }
    }

    /**
     * 菜单：发送键值点击事件
     *
     * @param event 点击事件
     */
    @FXML
    private void handlesendButtonValueAction(ActionEvent event) {
        if (sendButtonValue.isSelected() && displayButtonValue.isSelected()) {
            enterSend.setSelected(false);
        }
    }

    /**
     * 菜单：本地回显键值点击事件
     *
     * @param event 点击事件
     */
    @FXML
    private void handledisplayButtonValueAction(ActionEvent event) {
        if (sendButtonValue.isSelected() && displayButtonValue.isSelected()) {
            enterSend.setSelected(false);
        }
    }

    /**
     * 菜单：发送回车0x0a后自动附加换行符0x0d点击事件
     *
     * @param event 点击事件
     */
    @FXML
    private void handlesendEnterAndNewlineCharacterAction(ActionEvent event) {
    }


    /**
     * 菜单：发送帮助点击事件
     *
     * @param event 点击事件
     */
    @FXML
    private void handlesendHelpAction(ActionEvent event) {
        alert(AlertType.INFORMATION,"发送帮助", "需要帮助请联系作者！");
    }

    /**
     * 菜单：多字符串点击事件
     *
     * @param event 点击事件
     */
    @FXML
    private void handlemultipleStringsAction(ActionEvent event) {
    }

    /**
     * 菜单：小工具点击事件
     *
     * @param event 点击事件
     */
    @FXML
    private void handlegadgetAction(ActionEvent event) {
    }

    /**
     * 菜单：帮助点击事件
     *
     * @param event 点击事件
     */
    @FXML
    private void handlehelpAction(ActionEvent event) {
        alert(AlertType.INFORMATION,"帮助", "需要帮助请联系作者！");
    }

    /**
     * 菜单：联系作者点击事件
     *
     * @param event 点击事件
     */
    @FXML
    private void handlecontactAuthorAction(ActionEvent event) {
        alert(AlertType.INFORMATION,"联系作者", "邮箱：wxhntmy@163.com（非提交bug勿扰）");
    }

    /**
     * Alert 弹出窗口
     * @param type alert类型
     * @param title 标题
     * @param content 内容
     */
    public void alert(AlertType type, String title, String content){
        Alert alert = new Alert(type);
        //对话框标题
        alert.setTitle(title);
        alert.setHeaderText(null);
        //对话框内容
        alert.setContentText(content);
        alert.showAndWait();
    }
}
